/*
 * $Id: PerforceSynchronizer.java 23 2002-09-29 13:59:30Z bpruessmann $
 * 
 * Copyright (c) 2001 Boris Pruessmann and others.
 * All rights reserved.   This program and the accompanying materials
 * are made available under the terms of the Common Public License v0.5
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/cpl-v05.html
 * 
 * Contributors:
 *    Boris Pruessmann - Initial implementation.
 */
package net.sourceforge.p4eclipse.core;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import jdbm.JDBMRecordManager;
import net.sourceforge.p4eclipse.core.api.PerforceUtil;
import net.sourceforge.p4eclipse.core.syncinfo.FolderSyncInfo;
import net.sourceforge.p4eclipse.core.syncinfo.ResourceSyncInfo;
import net.sourceforge.p4eclipse.core.syncinfo.SimpleCache;
import net.sourceforge.p4eclipse.ui.PerforceUIPlugin;
import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceChangeEvent;
import org.eclipse.core.resources.IResourceChangeListener;
import org.eclipse.core.resources.IResourceDelta;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Path;

/**
  * @version 	$Revision: 23 $
  * @author		<a href="mailto:bpruessmann@users.sourceforge.net">Boris Pruessmann</a>
  */
public class PerforceSynchronizer implements IResourceChangeListener, IResourceStateChangeListener
{
	private static final int CACHE_ENTRY_TTL = Integer.MAX_VALUE;

	private static PerforceSynchronizer instance;
	private JDBMRecordManager recordManager;
	private SimpleCache folderSyncCache;
	private SimpleCache resourceSyncCache;	
	private PerforceSynchronizerRunnable synchronizerRunnable;
	
	private static class FolderSyncInfoCacheLoader implements SimpleCache.ICacheLoader
	{
		public SimpleCache.CacheData load(Object key, SimpleCache cache)
			throws IOException
		{
			Policy.assertInstanceOf(key, String.class);
			
			IPath folderPath = new Path((String)key);
			
			FolderSyncInfo result = null;
			FolderSyncInfo[] info = PerforceUtil.getFolderSyncInfo(folderPath.removeLastSegments(1).append("/*").toOSString());
			for (int i = 0, length = info.length; i < length; i++)
			{
				try
				{
					IPath localPath = new Path(PerforceUIPlugin.getClient().where(info[i].getDepotFileName() + "/ ").getLocalFilename());
					if (localPath.getDevice() != null)
						localPath = localPath.setDevice(localPath.getDevice().toUpperCase());
					localPath = localPath.removeLastSegments(1);
					
					if (!localPath.equals(folderPath))
					{					
						if (localPath.toFile().exists())
							cache.put(new SimpleCache.CacheData(localPath.toOSString(),
						    	                                info[i],
						    	                                CACHE_ENTRY_TTL));
					}
					else
						result = info[i];
				}
				catch (PerforceException e)
				{
					PerforceUIPlugin.log(e.getStatus());
				}
			}
			
			if (result == null)
				return null;
			else
				return new SimpleCache.CacheData(folderPath.toOSString(), result, CACHE_ENTRY_TTL);
		}
	}
	
	private static class ResourceSyncInfoCacheLoader implements SimpleCache.ICacheLoader
	{
		public SimpleCache.CacheData load(Object key, SimpleCache cache)
			throws IOException
		{
			Policy.assertInstanceOf(key, String.class);
			
			IPath filePath = new Path((String)key);
			
			ResourceSyncInfo result = null;
			ResourceSyncInfo[] info = PerforceUtil.getResourceSyncInfo(filePath.removeLastSegments(1).append("/*").toOSString());			
			for (int i = 0, length = info.length; i < length; i++)
			{
				IPath localFilePath = new Path(info[i].getClientFileName());
				if (localFilePath.getDevice() != null)
					localFilePath = localFilePath.setDevice(localFilePath.getDevice().toUpperCase());
				
				if (!localFilePath.equals(filePath))
				{					
					if (localFilePath.toFile().exists())
						cache.put(new SimpleCache.CacheData(localFilePath.toOSString(),
					    	                                info[i],
					    	                                CACHE_ENTRY_TTL));
				}
				else
					result = info[i];
				
			}		
			
			if (result == null)
				return new SimpleCache.CacheData(filePath.toOSString(), ResourceSyncInfo.NULL_INFO, CACHE_ENTRY_TTL);
			else
				return new SimpleCache.CacheData(filePath.toOSString(), result, CACHE_ENTRY_TTL);
		}
	}
	
	/**
	 * Constructor for PerforceSynchronizer.
	 */
	public PerforceSynchronizer()
	{	
		try
		{
			IPath stateLocation = PerforceUIPlugin.getPlugin().getStateLocation().append("cache"); //$NON-NLS-1$
			recordManager = new JDBMRecordManager(stateLocation.toOSString()); 
			
			folderSyncCache = new SimpleCache(recordManager, "folderCache"); //$NON-NLS-1$
			folderSyncCache.registerLoader(new FolderSyncInfoCacheLoader());
			
			resourceSyncCache = new SimpleCache(recordManager, "fileCache"); //$NON-NLS-1$
			resourceSyncCache.registerLoader(new ResourceSyncInfoCacheLoader());
		}
		catch (IOException e)
		{
			throw new RuntimeException("Cache creation failed. Reason: " + e.getMessage());
		}
		
		PerforceUIPlugin.getPlugin().addResourceStateChangeListener(this);
		ResourcesPlugin.getWorkspace().addResourceChangeListener(this, IResourceChangeEvent.POST_CHANGE);

		try
		{		
			synchronizerRunnable = new PerforceSynchronizerRunnable(recordManager);
			
			Thread t = new Thread(synchronizerRunnable, "Perforce Background Updates"); //$NON-NLS-1$
			t.start();
		}
		catch (PerforceException e)
		{
			PerforceUIPlugin.getPlugin().log(e.getStatus());
		}
		catch (IOException e)
		{
			PerforceUIPlugin.getPlugin().logError("PerforceSynchronizer.<init> failed.", e);
		}
	}
	
	/**
	  * Returns the PerforceSynchronizer singleton instance.
	  */
	public static PerforceSynchronizer getInstance()
	{
		Policy.assertNotNull(instance);
		return instance;
	}
	
	public static void startup()
	{
		Policy.assertNull(instance);
		instance = new PerforceSynchronizer();
	}
	
	public static void shutdown()
	{
		getInstance().synchronizerRunnable.shutdown();
		try
		{
			getInstance().recordManager.close();
		}
		catch (IOException e)
		{
			PerforceUIPlugin.getPlugin().logError("Error durint PerforceSynchronizer.shutdown.", e);
		}
	}
	
	public void reset()
	{
		try
		{
			folderSyncCache.clear();
			resourceSyncCache.clear();

			synchronizerRunnable.reset();
		}
		catch (IOException e)
		{
			PerforceUIPlugin.getPlugin().logError("PerforceSynchronizer.reset failed.", e);
		}
		catch (PerforceException e)
		{
			PerforceUIPlugin.getPlugin().log(e.getStatus());
		}
	}
		
	public FolderSyncInfo getFolderSync(IContainer container)
		throws PerforceException
	{
		return getFolderSyncInfo(container.getLocation().toFile());
	}
			
	public FolderSyncInfo getFolderSyncInfo(File file)
	{
		Policy.assertNotNull(file);

		try
		{		
			if (file.exists() && file.isDirectory())
			{
				IPath path = new Path(file.getAbsolutePath());
				if (path.getDevice() != null)
					path = path.setDevice(path.getDevice().toUpperCase());
				
				SimpleCache.CacheData data = folderSyncCache.get(path.toOSString());
				if (data != null)
					return (FolderSyncInfo)data.getValue();
			}
		}
		catch (IOException e)
		{
			PerforceUIPlugin.getPlugin().logError("PerforceSynchronizer.getFolderSyncInfo failed.", e);
		}
		
		return null;
	}
	
	public ResourceSyncInfo getResourceSync(IResource file)
		throws PerforceException	
	{
		Policy.assertNotNull(file);
		
		try
		{
			IContainer parentContainer = file.getParent();
			if (parentContainer != null)
			{
				if (parentContainer.exists())
				{
					IPath path = file.getLocation();
					if (path.getDevice() != null)
						path = path.setDevice(path.getDevice().toUpperCase());
					
					SimpleCache.CacheData data = resourceSyncCache.get(path.toOSString());
					if (data != null)
						return (ResourceSyncInfo)data.getValue();
				}
			}
		}
		catch (IOException e)
		{
			PerforceUIPlugin.getPlugin().logError("PerforceSynchronizer.getResourceSyncInfo failed.", e);
		}
		
		return null;
	}
	
	public void deleteFolderSync(IContainer container)
		throws PerforceException
	{
		removeFolderSyncInfo(container.getLocation());
	}
	
	
	void removeFolderSyncInfo(IPath folderPath)
	{
		try
		{
			if (folderPath.getDevice() != null)
				folderPath = folderPath.setDevice(folderPath.getDevice().toUpperCase());
			folderSyncCache.remove(folderPath.toOSString());
		}
		catch (IOException e)
		{
			PerforceUIPlugin.getPlugin().logError("PerforceSynchronizer.removeFolderSyncInfo failed.", e);
		}
	}
	
	void removeResourceSyncInfo(IPath filePath)
	{
		try
		{
			if (filePath.getDevice() != null)
				filePath = filePath.setDevice(filePath.getDevice().toUpperCase());
			resourceSyncCache.remove(filePath.toOSString());
		}
		catch (IOException e)
		{
			PerforceUIPlugin.getPlugin().logError("PerforceSynchronizer.removeResourceSyncInfo failed.", e);
		}
	}
	
	/*
	 * @see IResourceStateChangeListener#resourceStateChanged(IResource[])
	 */
	public void resourceStateChanged(IResource[] changedResources)
	{
		for (int i = 0, length = changedResources.length; i < length; i++)
		{
			IResource resource = changedResources[i];
			switch (resource.getType())
			{
				case IResource.PROJECT:
				case IResource.FOLDER:
					removeFolderSyncInfo(resource.getLocation());
					break;
					
				case IResource.FILE:
					removeResourceSyncInfo(resource.getLocation());
					break;
			}
		}
	}
	
	/*
	 * @see IResourceChangeListener#resourceChanged(IResourceChangeEvent)
	 */
	public void resourceChanged(IResourceChangeEvent event)
	{
		List changedResources = new ArrayList();
		
		IResourceDelta root = event.getDelta();
		IResourceDelta[] projectDeltas = root.getAffectedChildren(IResourceDelta.REMOVED | IResourceDelta.ADDED | IResourceDelta.CHANGED);
		
		for (int i = 0, iLength = projectDeltas.length; i < iLength; i++)
		{
			IResourceDelta delta = projectDeltas[i];
			
			IResource resource = delta.getResource();
			if (resource.getType() == IResource.PROJECT)
			{
				IResourceDelta[] children =	delta.getAffectedChildren(IResourceDelta.REMOVED | IResourceDelta.ADDED | IResourceDelta.CHANGED);
				for (int j = 0, jLength = children.length; j < jLength; j++)
					changedResources.add(children[j].getResource());
			}
				
		}
		
		PerforceUIPlugin.getPlugin().broadcastResourceStateChanges((IResource[])changedResources.toArray(new IResource[0]));		
	}
}
